`default_nettype none
/*
 *  SPDX-FileCopyrightText: 2015 Clifford Wolf
 *  PicoSoC - A simple example SoC using PicoRV32
 *
 *  Copyright (C) 2017  Clifford Wolf <clifford@clifford.at>
 *
 *  Permission to use, copy, modify, and/or distribute this software for any
 *  purpose with or without fee is hereby granted, provided that the above
 *  copyright notice and this permission notice appear in all copies.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *  SPDX-License-Identifier: ISC
 */

module spimemio_wb (
    input wb_clk_i,
    input wb_rst_i,

    input [31:0] wb_adr_i, 
    input [31:0] wb_dat_i,
    input [3:0] wb_sel_i,
    input wb_we_i,
    input wb_cyc_i,

    input wb_flash_stb_i,
    input wb_cfg_stb_i,

    output wb_flash_ack_o,
    output wb_cfg_ack_o,

    output [31:0] wb_flash_dat_o,
    output [31:0] wb_cfg_dat_o,

    output quad_mode,
    input  pass_thru,
    input  pass_thru_csb,
    input  pass_thru_sck,
    input  pass_thru_sdi,
    output pass_thru_sdo,

    output flash_csb,
    output flash_clk,

    output flash_csb_oeb,
    output flash_clk_oeb,

    output flash_io0_oeb,
    output flash_io1_oeb,
    output flash_io2_oeb,
    output flash_io3_oeb,

    output flash_csb_ieb,
    output flash_clk_ieb,

    output flash_io0_ieb,
    output flash_io1_ieb,
    output flash_io2_ieb,
    output flash_io3_ieb,

    output flash_io0_do,
    output flash_io1_do,
    output flash_io2_do,
    output flash_io3_do,

    input  flash_io0_di,
    input  flash_io1_di,
    input  flash_io2_di,
    input  flash_io3_di

);
    wire spimem_ready;
    wire [23:0] mem_addr;
    wire [31:0] spimem_rdata;
    wire [31:0] spimemio_cfgreg_do;
    wire [3:0] cfgreg_we;
    wire spimemio_cfgreg_sel;
    wire valid;
    wire resetn;
    wire quad_mode;

    assign resetn = ~wb_rst_i;
    assign valid = wb_cyc_i && wb_flash_stb_i;    
    assign wb_flash_ack_o = spimem_ready;
    assign wb_cfg_ack_o = spimemio_cfgreg_sel;

    assign mem_addr = wb_adr_i[23:0];
    assign spimemio_cfgreg_sel = wb_cyc_i && wb_cfg_stb_i;

    assign cfgreg_we = spimemio_cfgreg_sel ? wb_sel_i & {4{wb_we_i}} : 4'b 0000;
    assign wb_flash_dat_o = spimem_rdata;
    assign wb_cfg_dat_o = spimemio_cfgreg_do;

    spimemio spimemio (
        .clk    (wb_clk_i),
        .resetn (resetn),
        .valid  (valid),
        .ready  (spimem_ready),
        .addr   (mem_addr),
        .rdata  (spimem_rdata),

        .flash_csb    (flash_csb),
        .flash_clk    (flash_clk),

        .flash_csb_oeb (flash_csb_oeb),
        .flash_clk_oeb (flash_clk_oeb),

        .flash_io0_oeb (flash_io0_oeb),
        .flash_io1_oeb (flash_io1_oeb),
        .flash_io2_oeb (flash_io2_oeb),
        .flash_io3_oeb (flash_io3_oeb),

        .flash_csb_ieb (flash_csb_ieb),
        .flash_clk_ieb (flash_clk_ieb),

        .flash_io0_ieb (flash_io0_ieb),
        .flash_io1_ieb (flash_io1_ieb),
        .flash_io2_ieb (flash_io2_ieb),
        .flash_io3_ieb (flash_io3_ieb),

        .flash_io0_do (flash_io0_do),
        .flash_io1_do (flash_io1_do),
        .flash_io2_do (flash_io2_do),
        .flash_io3_do (flash_io3_do),

        .flash_io0_di (flash_io0_di),
        .flash_io1_di (flash_io1_di),
        .flash_io2_di (flash_io2_di),
        .flash_io3_di (flash_io3_di),

        .cfgreg_we(cfgreg_we),
        .cfgreg_di(wb_dat_i),
        .cfgreg_do(spimemio_cfgreg_do),

	.quad_mode(quad_mode),
	.pass_thru(pass_thru),
	.pass_thru_csb(pass_thru_csb),
	.pass_thru_sck(pass_thru_sck),
	.pass_thru_sdi(pass_thru_sdi),
	.pass_thru_sdo(pass_thru_sdo)
    );

endmodule

module spimemio (
    input clk, resetn,

    input valid,
    output ready,
    input [23:0] addr,
    output reg [31:0] rdata,

    output flash_csb,
    output flash_clk,

    output flash_csb_oeb,
    output flash_clk_oeb,

    output flash_io0_oeb,
    output flash_io1_oeb,
    output flash_io2_oeb,
    output flash_io3_oeb,

    output flash_csb_ieb,
    output flash_clk_ieb,

    output flash_io0_ieb,
    output flash_io1_ieb,
    output flash_io2_ieb,
    output flash_io3_ieb,

    output flash_io0_do,
    output flash_io1_do,
    output flash_io2_do,
    output flash_io3_do,

    input  flash_io0_di,
    input  flash_io1_di,
    input  flash_io2_di,
    input  flash_io3_di,

    input   [3:0] cfgreg_we,
    input  [31:0] cfgreg_di,
    output [31:0] cfgreg_do,

    output quad_mode,

    input  pass_thru,
    input  pass_thru_csb,
    input  pass_thru_sck,
    input  pass_thru_sdi,
    output pass_thru_sdo
);
    reg        xfer_resetn;
    reg        din_valid;
    wire       din_ready;
    reg  [7:0] din_data;
    reg  [3:0] din_tag;
    reg        din_cont;
    reg        din_qspi;
    reg        din_ddr;
    reg        din_rd;

    wire       dout_valid;
    wire [7:0] dout_data;
    wire [3:0] dout_tag;
    wire       quad_mode;

    reg [23:0] buffer;

    reg [23:0] rd_addr;
    reg rd_valid;
    reg rd_wait;
    reg rd_inc;

    assign ready = valid && (addr == rd_addr) && rd_valid;
    wire jump = valid && !ready && (addr != rd_addr+4) && rd_valid;

    reg softreset;

    reg       config_en;      // cfgreg[31]
    reg       config_ddr;     // cfgreg[22]
    reg       config_qspi;    // cfgreg[21]
    reg       config_cont;    // cfgreg[20]
    reg [3:0] config_dummy;   // cfgreg[19:16]
    reg [3:0] config_oe;      // cfgreg[11:8]
    reg       config_csb;     // cfgreg[5]
    reg       config_clk;     // cfgref[4]
    reg [3:0] config_do;      // cfgreg[3:0]

    assign cfgreg_do[31] = config_en;
    assign cfgreg_do[30:23] = 0;
    assign cfgreg_do[22] = config_ddr;
    assign cfgreg_do[21] = config_qspi;
    assign cfgreg_do[20] = config_cont;
    assign cfgreg_do[19:16] = config_dummy;
    assign cfgreg_do[15:12] = 0;
    assign cfgreg_do[11:8] = {~flash_io3_oeb, ~flash_io2_oeb, ~flash_io1_oeb, ~flash_io0_oeb};
    assign cfgreg_do[7:6] = 0;
    assign cfgreg_do[5] = flash_csb;
    assign cfgreg_do[4] = flash_clk;
    assign cfgreg_do[3:0] = {flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di};

    always @(posedge clk) begin
        softreset <= !config_en || cfgreg_we;
        if (!resetn) begin
            softreset <= 1;
            config_en <= 1;
            config_csb <= 0;
            config_clk <= 0;
            config_oe <= 0;
            config_do <= 0;
            config_ddr <= 0;
            config_qspi <= 0;
            config_cont <= 0;
            config_dummy <= 8;
        end else begin
            if (cfgreg_we[0]) begin
                config_csb <= cfgreg_di[5];
                config_clk <= cfgreg_di[4];
                config_do <= cfgreg_di[3:0];
            end
            if (cfgreg_we[1]) begin
                config_oe <= cfgreg_di[11:8];
            end
            if (cfgreg_we[2]) begin
                config_ddr <= cfgreg_di[22];
                config_qspi <= cfgreg_di[21];
                config_cont <= cfgreg_di[20];
                config_dummy <= cfgreg_di[19:16];
            end
            if (cfgreg_we[3]) begin
                config_en <= cfgreg_di[31];
            end
        end
    end

    wire xfer_csb;
    wire xfer_clk;

    wire xfer_io0_oe;
    wire xfer_io1_oe;
    wire xfer_io2_oe;
    wire xfer_io3_oe;

    wire xfer_io0_do;
    wire xfer_io1_do;
    wire xfer_io2_do;
    wire xfer_io3_do;

    reg xfer_io0_90;
    reg xfer_io1_90;
    reg xfer_io2_90;
    reg xfer_io3_90;

    always @(negedge clk) begin
        xfer_io0_90 <= xfer_io0_do;
        xfer_io1_90 <= xfer_io1_do;
        xfer_io2_90 <= xfer_io2_do;
        xfer_io3_90 <= xfer_io3_do;
    end

    wire pass_thru;
    wire pass_thru_csb;
    wire pass_thru_sck;
    wire pass_thru_sdi;
    wire pass_thru_sdo;

    assign quad_mode = config_qspi;

    assign flash_csb = (pass_thru) ? pass_thru_csb : (config_en ? xfer_csb : config_csb);
    assign flash_clk = (pass_thru) ? pass_thru_sck : (config_en ? xfer_clk : config_clk);

    assign flash_csb_oeb = (pass_thru) ? 1'b0 : (~resetn ? 1'b1 : 1'b0);
    assign flash_clk_oeb = (pass_thru) ? 1'b0 : (~resetn ? 1'b1 : 1'b0);

    assign flash_io0_oeb = pass_thru ? 1'b0 : ~resetn ? 1'b1 : (config_en ? ~xfer_io0_oe : ~config_oe[0]);
    assign flash_io1_oeb = (pass_thru | ~resetn) ? 1'b1 : (config_en ? ~xfer_io1_oe : ~config_oe[1]);
    assign flash_io2_oeb = (pass_thru | ~resetn) ? 1'b1 : (config_en ? ~xfer_io2_oe : ~config_oe[2]);
    assign flash_io3_oeb = (pass_thru | ~resetn) ? 1'b1 : (config_en ? ~xfer_io3_oe : ~config_oe[3]);
    assign flash_csb_ieb = 1'b1;	/* Always disabled */
    assign flash_clk_ieb = 1'b1;	/* Always disabled */

    assign flash_io0_ieb = (pass_thru | ~resetn) ? 1'b1 : (config_en ? xfer_io0_oe : config_oe[0]);
    assign flash_io1_ieb = pass_thru ? 1'b0 : ~resetn ? 1'b1 : (config_en ? xfer_io1_oe : config_oe[1]);
    assign flash_io2_ieb = (pass_thru | ~resetn) ? 1'b1 : (config_en ? xfer_io2_oe : config_oe[2]);
    assign flash_io3_ieb = (pass_thru | ~resetn) ? 1'b1 : (config_en ? xfer_io3_oe : config_oe[3]);

    assign flash_io0_do = pass_thru ? pass_thru_sdi : (config_en ? (config_ddr ? xfer_io0_90 : xfer_io0_do) : config_do[0]);
    assign flash_io1_do = config_en ? (config_ddr ? xfer_io1_90 : xfer_io1_do) : config_do[1];
    assign flash_io2_do = config_en ? (config_ddr ? xfer_io2_90 : xfer_io2_do) : config_do[2];
    assign flash_io3_do = config_en ? (config_ddr ? xfer_io3_90 : xfer_io3_do) : config_do[3];
    assign pass_thru_sdo = pass_thru ? flash_io1_di : 1'b0;

    wire xfer_dspi = din_ddr && !din_qspi;
    wire xfer_ddr = din_ddr && din_qspi;

    spimemio_xfer xfer (
        .clk          (clk         ),
        .resetn       (resetn 	   ),
        .xfer_resetn  (xfer_resetn ),
        .din_valid    (din_valid   ),
        .din_ready    (din_ready   ),
        .din_data     (din_data    ),
        .din_tag      (din_tag     ),
        .din_cont     (din_cont    ),
        .din_dspi     (xfer_dspi   ),
        .din_qspi     (din_qspi    ),
        .din_ddr      (xfer_ddr    ),
        .din_rd       (din_rd      ),
        .dout_valid   (dout_valid  ),
        .dout_data    (dout_data   ),
        .dout_tag     (dout_tag    ),
        .flash_csb    (xfer_csb    ),
        .flash_clk    (xfer_clk    ),
        .flash_io0_oe (xfer_io0_oe ),
        .flash_io1_oe (xfer_io1_oe ),
        .flash_io2_oe (xfer_io2_oe ),
        .flash_io3_oe (xfer_io3_oe ),
        .flash_io0_do (xfer_io0_do ),
        .flash_io1_do (xfer_io1_do ),
        .flash_io2_do (xfer_io2_do ),
        .flash_io3_do (xfer_io3_do ),
        .flash_io0_di (flash_io0_di),
        .flash_io1_di (flash_io1_di),
        .flash_io2_di (flash_io2_di),
        .flash_io3_di (flash_io3_di)
    );

    reg [3:0] state;

    always @(posedge clk) begin
        xfer_resetn <= 1;
        din_valid <= 0;

        if (!resetn || softreset) begin
            state <= 0;
            xfer_resetn <= 0;
            rd_valid <= 0;
            din_tag <= 0;
            din_cont <= 0;
            din_qspi <= 0;
            din_ddr <= 0;
            din_rd <= 0;
        end else begin
            if (dout_valid && dout_tag == 1) buffer[ 7: 0] <= dout_data;
            if (dout_valid && dout_tag == 2) buffer[15: 8] <= dout_data;
            if (dout_valid && dout_tag == 3) buffer[23:16] <= dout_data;
            if (dout_valid && dout_tag == 4) begin
                rdata <= {dout_data, buffer};
                rd_addr <= rd_inc ? rd_addr + 4 : addr;
                rd_valid <= 1;
                rd_wait <= rd_inc;
                rd_inc <= 1;
            end

            if (valid)
                rd_wait <= 0;

            case (state)
                0: begin
                    din_valid <= 1;
                    din_data <= 8'h ff;
                    din_tag <= 0;
                    if (din_ready) begin
                        din_valid <= 0;
                        state <= 1;
                    end
                end
                1: begin
                    if (dout_valid) begin
                        xfer_resetn <= 0;
                        state <= 2;
                    end
                end
                2: begin
                    din_valid <= 1;
                    din_data <= 8'h ab;
                    din_tag <= 0;
                    if (din_ready) begin
                        din_valid <= 0;
                        state <= 3;
                    end
                end
                3: begin
                    if (dout_valid) begin
                        xfer_resetn <= 0;
                        state <= 4;
                    end
                end
                4: begin
                    rd_inc <= 0;
                    din_valid <= 1;
                    din_tag <= 0;
                    case ({config_ddr, config_qspi})
                        2'b11: din_data <= 8'h ED;
                        2'b01: din_data <= 8'h EB;
                        2'b10: din_data <= 8'h BB;
                        2'b00: din_data <= 8'h 03;
                    endcase
                    if (din_ready) begin
                        din_valid <= 0;
                        state <= 5;
                    end
                end
                5: begin
                    if (valid && !ready) begin
                        din_valid <= 1;
                        din_tag <= 0;
                        din_data <= addr[23:16];
                        din_qspi <= config_qspi;
                        din_ddr <= config_ddr;
                        if (din_ready) begin
                            din_valid <= 0;
                            state <= 6;
                        end
                    end
                end
                6: begin
                    din_valid <= 1;
                    din_tag <= 0;
                    din_data <= addr[15:8];
                    if (din_ready) begin
                        din_valid <= 0;
                        state <= 7;
                    end
                end
                7: begin
                    din_valid <= 1;
                    din_tag <= 0;
                    din_data <= addr[7:0];
                    if (din_ready) begin
                        din_valid <= 0;
                        din_data <= 0;
                        state <= config_qspi || config_ddr ? 8 : 9;
                    end
                end
                8: begin
                    din_valid <= 1;
                    din_tag <= 0;
                    din_data <= config_cont ? 8'h A5 : 8'h FF;
                    if (din_ready) begin
                        din_rd <= 1;
                        din_data <= config_dummy;
                        din_valid <= 0;
                        state <= 9;
                    end
                end
                9: begin
                    din_valid <= 1;
                    din_tag <= 1;
                    if (din_ready) begin
                        din_valid <= 0;
                        state <= 10;
                    end
                end
                10: begin
                    din_valid <= 1;
                    din_data <= 8'h 00;
                    din_tag <= 2;
                    if (din_ready) begin
                        din_valid <= 0;
                        state <= 11;
                    end
                end
                11: begin
                    din_valid <= 1;
                    din_tag <= 3;
                    if (din_ready) begin
                        din_valid <= 0;
                        state <= 12;
                    end
                end
                12: begin
                    if (!rd_wait || valid) begin
                        din_valid <= 1;
                        din_tag <= 4;
                        if (din_ready) begin
                            din_valid <= 0;
                            state <= 9;
                        end
                    end
                end
            endcase

            if (jump) begin
                rd_inc <= 0;
                rd_valid <= 0;
                xfer_resetn <= 0;
                if (config_cont) begin
                    state <= 5;
                end else begin
                    state <= 4;
                    din_qspi <= 0;
                    din_ddr <= 0;
                end
                din_rd <= 0;
            end
        end
    end
endmodule

module spimemio_xfer (
    input clk, resetn, xfer_resetn,

    input            din_valid,
    output           din_ready,
    input      [7:0] din_data,
    input      [3:0] din_tag,
    input            din_cont,
    input            din_dspi,
    input            din_qspi,
    input            din_ddr,
    input            din_rd,

    output           dout_valid,
    output     [7:0] dout_data,
    output     [3:0] dout_tag,

    output reg flash_csb,
    output reg flash_clk,

    output reg flash_io0_oe,
    output reg flash_io1_oe,
    output reg flash_io2_oe,
    output reg flash_io3_oe,

    output reg flash_io0_do,
    output reg flash_io1_do,
    output reg flash_io2_do,
    output reg flash_io3_do,

    input      flash_io0_di,
    input      flash_io1_di,
    input      flash_io2_di,
    input      flash_io3_di
);
    reg [7:0] obuffer;
    reg [7:0] ibuffer;

    reg [3:0] count;
    reg [3:0] dummy_count;

    reg xfer_cont;
    reg xfer_dspi;
    reg xfer_qspi;
    reg xfer_ddr;
    reg xfer_ddr_q;
    reg xfer_rd;
    reg [3:0] xfer_tag;
    reg [3:0] xfer_tag_q;

    reg [7:0] next_obuffer;
    reg [7:0] next_ibuffer;
    reg [3:0] next_count;

    reg fetch;
    reg next_fetch;
    reg last_fetch;

    always @(posedge clk) begin
        xfer_ddr_q <= xfer_ddr;
        xfer_tag_q <= xfer_tag;
    end

    assign din_ready = din_valid && xfer_resetn && next_fetch;

    assign dout_valid = (xfer_ddr_q ? fetch && !last_fetch : next_fetch && !fetch) && xfer_resetn;
    assign dout_data = ibuffer;
    assign dout_tag = xfer_tag_q;

    always @* begin
        flash_io0_oe = 0;
        flash_io1_oe = 0;
        flash_io2_oe = 0;
        flash_io3_oe = 0;

        flash_io0_do = 0;
        flash_io1_do = 0;
        flash_io2_do = 0;
        flash_io3_do = 0;

        next_obuffer = obuffer;
        next_ibuffer = ibuffer;
        next_count = count;
        next_fetch = 0;

        if (dummy_count == 0) begin
            casez ({xfer_ddr, xfer_qspi, xfer_dspi})
                3'b 000: begin
                    flash_io0_oe = 1;
                    flash_io0_do = obuffer[7];

                    if (flash_clk) begin
                        next_obuffer = {obuffer[6:0], 1'b 0};
                        next_count = count - |count;
                    end else begin
                        next_ibuffer = {ibuffer[6:0], flash_io1_di};
                    end

                    next_fetch = (next_count == 0);
                end
                3'b 01?: begin
                    flash_io0_oe = !xfer_rd;
                    flash_io1_oe = !xfer_rd;
                    flash_io2_oe = !xfer_rd;
                    flash_io3_oe = !xfer_rd;

                    flash_io0_do = obuffer[4];
                    flash_io1_do = obuffer[5];
                    flash_io2_do = obuffer[6];
                    flash_io3_do = obuffer[7];

                    if (flash_clk) begin
                        next_obuffer = {obuffer[3:0], 4'b 0000};
                        next_count = count - {|count, 2'b00};
                    end else begin
                        next_ibuffer = {ibuffer[3:0], flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di};
                    end

                    next_fetch = (next_count == 0);
                end
                3'b 11?: begin
                    flash_io0_oe = !xfer_rd;
                    flash_io1_oe = !xfer_rd;
                    flash_io2_oe = !xfer_rd;
                    flash_io3_oe = !xfer_rd;

                    flash_io0_do = obuffer[4];
                    flash_io1_do = obuffer[5];
                    flash_io2_do = obuffer[6];
                    flash_io3_do = obuffer[7];

                    next_obuffer = {obuffer[3:0], 4'b 0000};
                    next_ibuffer = {ibuffer[3:0], flash_io3_di, flash_io2_di, flash_io1_di, flash_io0_di};
                    next_count = count - {|count, 2'b00};

                    next_fetch = (next_count == 0);
                end
                3'b ??1: begin
                    flash_io0_oe = !xfer_rd;
                    flash_io1_oe = !xfer_rd;

                    flash_io0_do = obuffer[6];
                    flash_io1_do = obuffer[7];

                    if (flash_clk) begin
                        next_obuffer = {obuffer[5:0], 2'b 00};
                        next_count = count - {|count, 1'b0};
                    end else begin
                        next_ibuffer = {ibuffer[5:0], flash_io1_di, flash_io0_di};
                    end

                    next_fetch = (next_count == 0);
                end
            endcase
        end
    end

    always @(posedge clk) begin
        if (!resetn || !xfer_resetn) begin
            fetch <= 1;
            last_fetch <= 1;
            flash_csb <= 1;
            flash_clk <= 0;
            count <= 0;
            dummy_count <= 0;
            xfer_tag <= 0;
            xfer_cont <= 0;
            xfer_dspi <= 0;
            xfer_qspi <= 0;
            xfer_ddr <= 0;
            xfer_rd <= 0;
        end else begin
            fetch <= next_fetch;
            last_fetch <= xfer_ddr ? fetch : 1;
            if (dummy_count) begin
                flash_clk <= !flash_clk && !flash_csb;
                dummy_count <= dummy_count - flash_clk;
            end else
            if (count) begin
                flash_clk <= !flash_clk && !flash_csb;
                obuffer <= next_obuffer;
                ibuffer <= next_ibuffer;
                count <= next_count;
            end
            if (din_valid && din_ready) begin
                flash_csb <= 0;
                flash_clk <= 0;

                count <= 8;
                dummy_count <= din_rd ? din_data : 0;
                obuffer <= din_data;

                xfer_tag <= din_tag;
                xfer_cont <= din_cont;
                xfer_dspi <= din_dspi;
                xfer_qspi <= din_qspi;
                xfer_ddr <= din_ddr;
                xfer_rd <= din_rd;
            end
        end
    end
endmodule

`default_nettype wire
